//
//  Messenger.swift
//  Link
//
//  Created by melvyn on 2018/9/6.
//  Copyright © 2018 NEET. All rights reserved.
//

import Foundation
import Alamofire
import RxSwift
import Starscream
import RxStarscream

open class Messenger {
    
    public let socket: WebSocket
    
    public let id: Int
    private var disposeBag = DisposeBag()
    
    public init(_ id: Int) {
        self.id = id
        
        let request = SessionManager.default.request(kApiRelativeURL + "/api/lives/\(id)").request!
        
        socket = WebSocket.init(request: request)
        
        // Refresh auth token
        NotificationCenter.default.rx.notification(.userDidLoggedIn)
            .subscribe(onNext: { [weak self] in
                guard let token = $0.object as? String else { return }
                self?.shutdown()
                self?.socket.request.setValue("Bearer " + token, forHTTPHeaderField: "Authorization")
                self?.open()
            })
        .disposed(by: disposeBag)
    }
    
    public func open() {
        socket.connect()
    }
    
    public func shutdown() {
        socket.disconnect()
    }
    
    public var isConnected: Observable<Bool> {
        return socket.rx.connected
    }
    
    /// Write comment to a in progress Live stream
    ///
    /// - Parameters:
    ///   - comment: comment info descriptor
    ///   - completionHandler: completion handler
    public func compose(_ comment: Comment.MultipartForm) -> Observable<Result<Bool>> {
        do {
            func encode<T>(_ value: T, event: StreamEvent) throws -> Data where T: Encodable {
                var data = try JSONEncoder().encode(value)
                data.insert(UInt8.init(event: event), at: 0)
                return data
            }
            
            return socket.rx.write(data: try encode(comment, event: .compose)).map({ _ in .success(true) })
        } catch let error {
            let result = Result<Bool>.failure(AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)))
            return Observable.just(result)
        }
    }
    
    public func comments() -> Observable<Result<[Comment]>> {
        return socket.rx.response
            .map({ response -> Data? in
                switch response {
                case .data(let data): return data
                default: return nil
                }
            })
            .unwrap()
            .map({
                guard $0.count > 1 else { return .failure(AFError.responseValidationFailed(reason: .dataFileNil)) }
                
                let code = $0[0]
                let metadata = $0.advanced(by: 1)
                
                let event = StreamEvent.init(code: code)
                switch event {
                case .comments, .compose:
                    do {
                        let comments = try JSONAdapter.models(of: Comment.self, from: metadata)
                        return .success(comments)
                    } catch let error {
                        print(error)
                        return .success([])
                    }
                case .error:
                    return .failure(APIError.socketFailed(reason: .unauthorized))
                default: return .success([])
                }
            })
    }
}
